/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.configuration; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import junit.framework.TestCase; import org.apache.commons.configuration.event.ConfigurationEvent; import org.apache.commons.configuration.event.ConfigurationListener; import org.apache.commons.configuration.reloading.FileAlwaysReloadingStrategy; /** * Test loading multiple configurations. * * @version $Id: TestCompositeConfiguration.java 705028 2008-10-15 20:33:35Z oheger $ */ public class TestCompositeConfiguration extends TestCase { /** Constant for a test property to be checked.*/ private static final String TEST_PROPERTY = "test.source.property"; protected PropertiesConfiguration conf1; protected PropertiesConfiguration conf2; protected XMLConfiguration xmlConf; protected CompositeConfiguration cc; /** * The File that we test with */ private String testProperties = new File("conf/test.properties").getAbsolutePath(); private String testProperties2 = new File("conf/test2.properties").getAbsolutePath(); private String testPropertiesXML = new File("conf/test.xml").getAbsolutePath(); protected void setUp() throws Exception { cc = new CompositeConfiguration(); conf1 = new PropertiesConfiguration(testProperties); conf2 = new PropertiesConfiguration(testProperties2); xmlConf = new XMLConfiguration(new File(testPropertiesXML)); cc.setThrowExceptionOnMissing(true); } public void testThrowExceptionOnMissing() { assertTrue("Throw Exception Property is not set!", cc.isThrowExceptionOnMissing()); } public void testAddRemoveConfigurations() throws Exception { cc.addConfiguration(conf1); assertEquals("Number of configurations", 2, cc.getNumberOfConfigurations()); cc.addConfiguration(conf1); assertEquals("Number of configurations", 2, cc.getNumberOfConfigurations()); cc.addConfiguration(conf2); assertEquals("Number of configurations", 3, cc.getNumberOfConfigurations()); cc.removeConfiguration(conf1); assertEquals("Number of configurations", 2, cc.getNumberOfConfigurations()); cc.clear(); assertEquals("Number of configurations", 1, cc.getNumberOfConfigurations()); } public void testGetPropertyWIncludes() throws Exception { cc.addConfiguration(conf1); cc.addConfiguration(conf2); List l = cc.getList("packages"); assertTrue(l.contains("packagea")); } public void testGetProperty() throws Exception { cc.addConfiguration(conf1); cc.addConfiguration(conf2); assertEquals("Make sure we get the property from conf1 first", "test.properties", cc.getString("propertyInOrder")); cc.clear(); cc.addConfiguration(conf2); cc.addConfiguration(conf1); assertEquals("Make sure we get the property from conf2 first", "test2.properties", cc.getString("propertyInOrder")); } public void testCantRemoveMemoryConfig() throws Exception { cc.clear(); assertEquals(1, cc.getNumberOfConfigurations()); Configuration internal = cc.getConfiguration(0); cc.removeConfiguration(internal); assertEquals(1, cc.getNumberOfConfigurations()); } public void testGetPropertyMissing() throws Exception { cc.addConfiguration(conf1); cc.addConfiguration(conf2); try { assertNull(cc.getString("bogus.property")); fail("Should have thrown a NoSuchElementException"); } catch (NoSuchElementException nsee) { assertTrue(nsee.getMessage().indexOf("bogus.property") > -1); } assertTrue("Should be false", !cc.getBoolean("test.missing.boolean", false)); assertTrue("Should be true", cc.getBoolean("test.missing.boolean.true", true)); } /** * Tests <code>List</code> parsing. */ public void testMultipleTypesOfConfigs() throws Exception { cc.addConfiguration(conf1); cc.addConfiguration(xmlConf); assertEquals("Make sure we get the property from conf1 first", 1, cc.getInt("test.short")); cc.clear(); cc.addConfiguration(xmlConf); cc.addConfiguration(conf1); assertEquals("Make sure we get the property from xml", 8, cc.getInt("test.short")); } /** * Tests <code>List</code> parsing. */ public void testPropertyExistsInOnlyOneConfig() throws Exception { cc.addConfiguration(conf1); cc.addConfiguration(xmlConf); assertEquals("value", cc.getString("element")); } /** * Tests getting a default when the key doesn't exist */ public void testDefaultValueWhenKeyMissing() throws Exception { cc.addConfiguration(conf1); cc.addConfiguration(xmlConf); assertEquals("default", cc.getString("bogus", "default")); assertTrue(1.4 == cc.getDouble("bogus", 1.4)); assertTrue(1.4 == cc.getDouble("bogus", 1.4)); } /** * Tests <code>List</code> parsing. */ public void testGettingConfiguration() throws Exception { cc.addConfiguration(conf1); cc.addConfiguration(xmlConf); assertEquals(PropertiesConfiguration.class, cc.getConfiguration(0).getClass()); assertEquals(XMLConfiguration.class, cc.getConfiguration(1).getClass()); } /** * Tests setting values. These are set in memory mode only! */ public void testClearingProperty() throws Exception { cc.addConfiguration(conf1); cc.addConfiguration(xmlConf); cc.clearProperty("test.short"); assertTrue("Make sure test.short is gone!", !cc.containsKey("test.short")); } /** * Tests adding values. Make sure they _DON'T_ override any other properties but add to the * existing properties and keep sequence */ public void testAddingProperty() throws Exception { cc.addConfiguration(conf1); cc.addConfiguration(xmlConf); String[] values = cc.getStringArray("test.short"); assertEquals("Number of values before add is wrong!", 1, values.length); assertEquals("First Value before add is wrong", "1", values[0]); cc.addProperty("test.short", "88"); values = cc.getStringArray("test.short"); assertEquals("Number of values is wrong!", 2, values.length); assertEquals("First Value is wrong", "1", values[0]); assertEquals("Third Value is wrong", "88", values[1]); } /** * Tests setting values. These are set in memory mode only! */ public void testSettingMissingProperty() throws Exception { cc.addConfiguration(conf1); cc.addConfiguration(xmlConf); cc.setProperty("my.new.property", "supernew"); assertEquals("supernew", cc.getString("my.new.property")); } /** * Tests retrieving subsets of configurations */ public void testGettingSubset() throws Exception { cc.addConfiguration(conf1); cc.addConfiguration(xmlConf); Configuration subset = cc.subset("test"); assertNotNull(subset); assertFalse("Shouldn't be empty", subset.isEmpty()); assertEquals("Make sure the initial loaded configs subset overrides any later add configs subset", "1", subset.getString("short")); cc.setProperty("test.short", "43"); subset = cc.subset("test"); assertEquals("Make sure the initial loaded configs subset overrides any later add configs subset", "43", subset.getString("short")); } /** * Tests subsets and still can resolve elements */ public void testSubsetCanResolve() throws Exception { cc = new CompositeConfiguration(); final BaseConfiguration config = new BaseConfiguration(); config.addProperty("subset.tempfile", "${java.io.tmpdir}/file.tmp"); cc.addConfiguration(config); cc.addConfiguration(ConfigurationConverter.getConfiguration(System.getProperties())); Configuration subset = cc.subset("subset"); assertEquals(System.getProperty("java.io.tmpdir") + "/file.tmp", subset.getString("tempfile")); } /** * Tests <code>List</code> parsing. */ public void testList() throws Exception { cc.addConfiguration(conf1); cc.addConfiguration(xmlConf); List packages = cc.getList("packages"); // we should get 3 packages here assertEquals(3, packages.size()); List defaultList = new ArrayList(); defaultList.add("1"); defaultList.add("2"); packages = cc.getList("packages.which.dont.exist", defaultList); // we should get 2 packages here assertEquals(2, packages.size()); } /** * Tests <code>String</code> array parsing. */ public void testStringArray() throws Exception { cc.addConfiguration(conf1); cc.addConfiguration(xmlConf); String[] packages = cc.getStringArray("packages"); // we should get 3 packages here assertEquals(3, packages.length); packages = cc.getStringArray("packages.which.dont.exist"); // we should get 0 packages here assertEquals(0, packages.length); } public void testGetList() { Configuration conf1 = new BaseConfiguration(); conf1.addProperty("array", "value1"); conf1.addProperty("array", "value2"); Configuration conf2 = new BaseConfiguration(); conf2.addProperty("array", "value3"); conf2.addProperty("array", "value4"); cc.addConfiguration(conf1); cc.addConfiguration(conf2); // check the composite 'array' property List list = cc.getList("array"); assertNotNull("null list", list); assertEquals("list size", 2, list.size()); assertTrue("'value1' not found in the list", list.contains("value1")); assertTrue("'value2' not found in the list", list.contains("value2")); // add an element to the list in the composite configuration cc.addProperty("array", "value5"); // test the new list list = cc.getList("array"); assertNotNull("null list", list); assertEquals("list size", 3, list.size()); assertTrue("'value1' not found in the list", list.contains("value1")); assertTrue("'value2' not found in the list", list.contains("value2")); assertTrue("'value5' not found in the list", list.contains("value5")); } /** * Tests <code>getKeys</code> preserves the order */ public void testGetKeysPreservesOrder() throws Exception { cc.addConfiguration(conf1); List orderedList = new ArrayList(); for (Iterator keys = conf1.getKeys(); keys.hasNext();) { orderedList.add(keys.next()); } List iteratedList = new ArrayList(); for (Iterator keys = cc.getKeys(); keys.hasNext();) { iteratedList.add(keys.next()); } assertEquals(orderedList.size(), iteratedList.size()); for (int i = 0; i < orderedList.size(); i++) { assertEquals(orderedList.get(i), iteratedList.get(i)); } } /** * Tests <code>getKeys(String key)</code> preserves the order */ public void testGetKeys2PreservesOrder() throws Exception { cc.addConfiguration(conf1); List orderedList = new ArrayList(); for (Iterator keys = conf1.getKeys("test"); keys.hasNext();) { orderedList.add(keys.next()); } List iteratedList = new ArrayList(); for (Iterator keys = cc.getKeys("test"); keys.hasNext();) { iteratedList.add(keys.next()); } assertEquals(orderedList.size(), iteratedList.size()); for (int i = 0; i < orderedList.size(); i++) { assertEquals(orderedList.get(i), iteratedList.get(i)); } } public void testGetStringWithDefaults() { BaseConfiguration defaults = new BaseConfiguration(); defaults.addProperty("default", "default string"); CompositeConfiguration c = new CompositeConfiguration(defaults); c.setThrowExceptionOnMissing(cc.isThrowExceptionOnMissing()); c.addProperty("string", "test string"); assertEquals("test string", c.getString("string")); try { c.getString("XXX"); fail("Should throw NoSuchElementException exception"); } catch (NoSuchElementException e) { //ok } catch (Exception e) { fail("Should throw NoSuchElementException exception, not " + e); } //test defaults assertEquals("test string", c.getString("string", "some default value")); assertEquals("default string", c.getString("default")); assertEquals("default string", c.getString("default", "some default value")); assertEquals("some default value", c.getString("XXX", "some default value")); } public void testCheckingInMemoryConfiguration() throws Exception { String TEST_KEY = "testKey"; Configuration defaults = new PropertiesConfiguration(); defaults.setProperty(TEST_KEY, "testValue"); Configuration testConfiguration = new CompositeConfiguration(defaults); assertTrue(testConfiguration.containsKey(TEST_KEY)); assertFalse(testConfiguration.isEmpty()); boolean foundTestKey = false; Iterator i = testConfiguration.getKeys(); //assertTrue(i instanceof IteratorChain); //IteratorChain ic = (IteratorChain)i; //assertEquals(2,i.size()); for (; i.hasNext();) { String key = (String) i.next(); if (key.equals(TEST_KEY)) { foundTestKey = true; } } assertTrue(foundTestKey); testConfiguration.clearProperty(TEST_KEY); assertFalse(testConfiguration.containsKey(TEST_KEY)); } public void testStringArrayInterpolation() { CompositeConfiguration config = new CompositeConfiguration(); config.addProperty("base", "foo"); config.addProperty("list", "${base}.bar1"); config.addProperty("list", "${base}.bar2"); config.addProperty("list", "${base}.bar3"); String[] array = config.getStringArray("list"); assertEquals("size", 3, array.length); assertEquals("1st element", "foo.bar1", array[0]); assertEquals("2nd element", "foo.bar2", array[1]); assertEquals("3rd element", "foo.bar3", array[2]); } /** * Tests whether global interpolation works with lists. */ public void testListInterpolation() { PropertiesConfiguration c1 = new PropertiesConfiguration(); c1.addProperty("c1.value", "test1"); c1.addProperty("c1.value", "${c2.value}"); cc.addConfiguration(c1); PropertiesConfiguration c2 = new PropertiesConfiguration(); c2.addProperty("c2.value", "test2"); cc.addConfiguration(c2); List lst = cc.getList("c1.value"); assertEquals("Wrong list size", 2, lst.size()); assertEquals("Wrong first element", "test1", lst.get(0)); assertEquals("Wrong second element", "test2", lst.get(1)); } /** * Tests interpolation in combination with reloading. */ public void testInterpolationWithReload() throws IOException, ConfigurationException { File testFile = new File("target/testConfig.properties"); final String propFirst = "first.name"; final String propFull = "full.name"; try { writeTestConfig(testFile, propFirst, "John"); PropertiesConfiguration c1 = new PropertiesConfiguration(testFile); c1.setReloadingStrategy(new FileAlwaysReloadingStrategy()); PropertiesConfiguration c2 = new PropertiesConfiguration(); c2.addProperty(propFull, "${" + propFirst + "} Doe"); CompositeConfiguration cc = new CompositeConfiguration(); cc.addConfiguration(c1); cc.addConfiguration(c2); assertEquals("Wrong name", "John Doe", cc.getString(propFull)); writeTestConfig(testFile, propFirst, "Jane"); assertEquals("First name not changed", "Jane", c1 .getString(propFirst)); assertEquals("First name not changed in composite", "Jane", cc .getString(propFirst)); assertEquals("Full name not changed", "Jane Doe", cc .getString(propFull)); } finally { if (testFile.exists()) { testFile.delete(); } } } /** * Writes a test properties file containing a single property definition. * * @param f the file to write * @param prop the property name * @param value the property value * @throws IOException if an error occurs */ private void writeTestConfig(File f, String prop, String value) throws IOException { PrintWriter out = new PrintWriter(new FileWriter(f)); out.print(prop); out.print("="); out.println(value); out.close(); } public void testInstanciateWithCollection() { Collection configs = new ArrayList(); configs.add(xmlConf); configs.add(conf1); configs.add(conf2); CompositeConfiguration config = new CompositeConfiguration(configs); assertEquals("Number of configurations", 4, config.getNumberOfConfigurations()); assertTrue("The in memory configuration is not empty", config.getInMemoryConfiguration().isEmpty()); } public void testClone() { CompositeConfiguration cc2 = (CompositeConfiguration) cc.clone(); assertEquals("Wrong number of contained configurations", cc .getNumberOfConfigurations(), cc2.getNumberOfConfigurations()); StrictConfigurationComparator comp = new StrictConfigurationComparator(); for (int i = 0; i < cc.getNumberOfConfigurations(); i++) { assertEquals("Wrong configuration class at " + i, cc .getConfiguration(i).getClass(), cc2.getConfiguration(i) .getClass()); assertNotSame("Configuration was not cloned", cc .getConfiguration(i), cc2.getConfiguration(i)); assertTrue("Configurations at " + i + " not equal", comp.compare(cc .getConfiguration(i), cc2.getConfiguration(i))); } assertTrue("Configurations are not equal", comp.compare(cc, cc2)); } /** * Tests cloning if one of the contained configurations does not support * this operation. This should cause an exception. */ public void testCloneNotSupported() { cc.addConfiguration(new NonCloneableConfiguration()); try { cc.clone(); fail("Could clone non cloneable configuration!"); } catch (ConfigurationRuntimeException crex) { // ok } } /** * Ensures that event listeners are not cloned. */ public void testCloneEventListener() { cc.addConfigurationListener(new TestEventListenerImpl()); CompositeConfiguration cc2 = (CompositeConfiguration) cc.clone(); assertTrue("Listeners have been cloned", cc2 .getConfigurationListeners().isEmpty()); } /** * Tests whether add property events are triggered. */ public void testEventAddProperty() { TestEventListenerImpl l = new TestEventListenerImpl(); cc.addConfigurationListener(l); cc.addProperty("test", "value"); assertEquals("No add events received", 2, l.eventCount); } /** * Tests whether set property events are triggered. */ public void testEventSetProperty() { TestEventListenerImpl l = new TestEventListenerImpl(); cc.addConfigurationListener(l); cc.setProperty("test", "value"); assertEquals("No set events received", 2, l.eventCount); } /** * Tests whether clear property events are triggered. */ public void testEventClearProperty() { cc.addConfiguration(conf1); assertTrue("Wrong value for property", cc .getBoolean("configuration.loaded")); TestEventListenerImpl l = new TestEventListenerImpl(); cc.addConfigurationListener(l); cc.clearProperty("configuration.loaded"); assertFalse("Key still present", cc.containsKey("configuration.loaded")); assertEquals("No clear events received", 2, l.eventCount); } /** * Tests chaning the list delimiter character. */ public void testSetListDelimiter() { cc.setListDelimiter('/'); checkSetListDelimiter(); } /** * Tests whether the correct list delimiter is set after a clear operation. */ public void testSetListDelimiterAfterClear() { cc.setListDelimiter('/'); cc.clear(); checkSetListDelimiter(); } /** * Helper method for testing whether the list delimiter is correctly * handled. */ private void checkSetListDelimiter() { cc.addProperty("test.list", "a/b/c"); cc.addProperty("test.property", "a,b,c"); assertEquals("Wrong number of list elements", 3, cc .getList("test.list").size()); assertEquals("Wrong value of property", "a,b,c", cc .getString("test.property")); } /** * Tests whether list splitting can be disabled. */ public void testSetDelimiterParsingDisabled() { cc.setDelimiterParsingDisabled(true); checkSetListDelimiterParsingDisabled(); } /** * Tests whether the list parsing flag is correctly handled after a clear() * operation. */ public void testSetDelimiterParsingDisabledAfterClear() { cc.setDelimiterParsingDisabled(true); cc.clear(); checkSetListDelimiterParsingDisabled(); } /** * Helper method for checking whether the list parsing flag is correctly * handled. */ private void checkSetListDelimiterParsingDisabled() { cc.addProperty("test.property", "a,b,c"); assertEquals("Wrong value of property", "a,b,c", cc .getString("test.property")); } /** * Prepares a test of the getSource() method. */ private void setUpSourceTest() { cc.addConfiguration(conf1); cc.addConfiguration(conf2); } /** * Tests the getSource() method if the property is defined in a single child * configuration. */ public void testGetSourceSingle() { setUpSourceTest(); conf1.addProperty(TEST_PROPERTY, Boolean.TRUE); assertSame("Wrong source configuration", conf1, cc .getSource(TEST_PROPERTY)); } /** * Tests the getSource() method for an unknown property key. */ public void testGetSourceUnknown() { setUpSourceTest(); assertNull("Wrong source for unknown key", cc.getSource(TEST_PROPERTY)); } /** * Tests the getSource() method for a property contained in the in memory * configuration. */ public void testGetSourceInMemory() { setUpSourceTest(); cc.addProperty(TEST_PROPERTY, Boolean.TRUE); assertSame("Source not found in in-memory config", cc .getInMemoryConfiguration(), cc.getSource(TEST_PROPERTY)); } /** * Tests the getSource() method if the property is defined by multiple child * configurations. In this case an exception should be thrown. */ public void testGetSourceMultiple() { setUpSourceTest(); conf1.addProperty(TEST_PROPERTY, Boolean.TRUE); cc.addProperty(TEST_PROPERTY, "a value"); try { cc.getSource(TEST_PROPERTY); fail("Property in multiple configurations did not cause an error!"); } catch (IllegalArgumentException iex) { // ok } } /** * Tests the getSource() method for a null key. This should cause an * exception. */ public void testGetSourceNull() { try { cc.getSource(null); fail("Could pass null key to getSource()!"); } catch (IllegalArgumentException iex) { // ok } } /** * Prepares a test for interpolation with multiple configurations and * similar properties. */ private void prepareInterpolationTest() { PropertiesConfiguration p = new PropertiesConfiguration(); p.addProperty("foo", "initial"); p.addProperty("bar", "${foo}"); p.addProperty("prefix.foo", "override"); cc.addConfiguration(p.subset("prefix")); cc.addConfiguration(p); assertEquals("Wrong value on direct access", "override", cc .getString("bar")); } /** * Tests querying a list when a tricky interpolation is involved. This is * related to CONFIGURATION-339. */ public void testGetListWithInterpolation() { prepareInterpolationTest(); List lst = cc.getList("bar"); assertEquals("Wrong number of values", 1, lst.size()); assertEquals("Wrong value in list", "override", lst.get(0)); } /** * Tests querying a string array when a tricky interpolation is involved. */ public void testGetStringArrayWithInterpolation() { prepareInterpolationTest(); String[] values = cc.getStringArray("bar"); assertEquals("Wrong number of values", 1, values.length); assertEquals("Wrong value in array", "override", values[0]); } /** * A test configuration event listener that counts the number of received * events. Used for testing the event facilities. */ static class TestEventListenerImpl implements ConfigurationListener { /** The number of received events.*/ int eventCount; public void configurationChanged(ConfigurationEvent event) { eventCount++; } } }